package com.jltx.zeus.viewmodel.ext

import android.app.Activity
import android.app.Application
import androidx.annotation.MainThread
import androidx.fragment.app.Fragment
import androidx.lifecycle.*
import java.lang.RuntimeException
import java.lang.reflect.InvocationTargetException
import kotlin.reflect.KClass

/**
 * @author jltxseo
 *         Created by junlintianxia on 2021/08/12.
 */
@MainThread
inline fun <reified VM : ViewModel> ViewModelStoreOwner.activityViewModels(
    key: String? = null,
    noinline parameters: ParameterDefinition = emptyParameterDefinition()
): Lazy<VM> {
    val act: Activity = when (this) {
        is Activity -> {
            this
        }
        is Fragment -> {
            this.requireActivity()
        }
        else -> throw IllegalArgumentException(
            "ViewModel can be accessed only when Activity is null"
        )
    }
    return createViewModelLazy(
        act,
        VM::class,
        key,
        {
            act as? ViewModelStoreOwner
        },
        parameters
    )
}

@MainThread
inline fun <reified VM : ViewModel> ViewModelStoreOwner.viewModels(
    key: String? = null,
    noinline from: ViewModelStoreOwnerDefinition? = null,
    noinline parameters: ParameterDefinition = emptyParameterDefinition()
): Lazy<VM> {
    val act: Activity = when (this) {
        is Activity -> {
            this
        }
        is Fragment -> {
            this.requireActivity()
        }
        else -> throw IllegalArgumentException(
            "ViewModel can be accessed only when Activity is null"
        )
    }
    return createViewModelLazy(
        act,
        VM::class,
        key,
        from,
        parameters
    )
}

@MainThread
fun <VM : ViewModel> ViewModelStoreOwner.createViewModelLazy(
    activity: Activity,
    viewModelClass: KClass<VM>,
    key: String? = null,
    from: ViewModelStoreOwnerDefinition? = null,
    parameters: ParameterDefinition = emptyParameterDefinition()
): Lazy<VM> {
    return CustomViewModelLazy(viewModelClass, key, {
        from?.invoke()?.viewModelStore ?: (this as? ViewModelStoreOwner)?.viewModelStore
        ?: throw IllegalStateException(
            "had not viewModelStore is attached"
        )
    }, {
        val application = activity.application ?: throw IllegalStateException(
            "ViewModel can be accessed only when Scene is attached"
        )

        val paramsList = parameters.invoke()
        val lastParams = paramsList.lastElement()
        if (AndroidViewModel::class.java.isAssignableFrom(viewModelClass.java) && !(lastParams?.modelClass != null && Application::class.java.isAssignableFrom(
                lastParams.modelClass.java
            ))
        ) {
            paramsList.addElement(parameter<Application>(application))
        }
        if (paramsList.size() > 1) {
            ParameterDefinitionViewModelFactory(application, paramsList)
        } else {
            ViewModelProvider.AndroidViewModelFactory.getInstance(application)
        }
    })
}

/**
 * Function to define a ViewModelStoreOwner
 */
typealias ViewModelStoreOwnerDefinition = () -> ViewModelStoreOwner?

class ParameterDefinitionViewModelFactory(
    application: Application,
    private val parameters: ParameterList?
) :
    ViewModelProvider.AndroidViewModelFactory(application) {

    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (parameters?.size() ?: 0 > 0) {
            return try {
                var clsList = arrayOfNulls<Class<*>>(parameters?.values?.size ?: 0)
                var objAry = arrayOfNulls<Any>(parameters?.values?.size ?: 0)
                parameters?.values?.forEachIndexed { index, parameterBean ->
                    clsList[index] = parameterBean.modelClass.java
                    objAry[index] = parameterBean.value
                }
                modelClass.getConstructor(*clsList).newInstance(*objAry)
            } catch (e: NoSuchMethodException) {
                throw RuntimeException("Cannot create an instance of $modelClass", e)
            } catch (e: IllegalAccessException) {
                throw RuntimeException("Cannot create an instance of $modelClass", e)
            } catch (e: InstantiationException) {
                throw RuntimeException("Cannot create an instance of $modelClass", e)
            } catch (e: InvocationTargetException) {
                throw RuntimeException("Cannot create an instance of $modelClass", e)
            }
        }
        return super.create(modelClass)
    }
}